/*
 * Graph.java
 *
 * Created on July 29, 2007, 7:04 PM
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


package base;

import tal.drivechain.variable.types.DataType;
import tal.drivechain.variable.Variable;
import tal.drivechain.matchers.VarMatcher;
import tal.drivechain.parsing.Range;
import tal.drivechain.plugin.Channel;
import tal.drivechain.plugin.Module;
import tal.drivechain.variable.SetProperty;
import tal.drivechain.variable.VarTableAction;

/**
 *
 * should be :::
 *  Graph { trigger;stopTrigger;loop;function={x};range (of input values);stepSize or stepCount
 * @author Main
 */
public class Graph extends Module {
    private VarMatcher startTrigger = null;
    private VarMatcher stopTrigger = null;
    private int stepCount = -1;
    private double stepSize = -1;
    private Variable output = null;
    private long stepDelay = -1;
    private int stepDelayNano = -1;
    private boolean running = false;
    private boolean loop;
    private Range range;
    private GraphThread gThread = null;
    private Variable stepVar = null;
    private VarTableAction setStepVar;
    
    @Override
    public void setupProperties() {
        setStartTrigger(props.getProperty("startTrigger"), null);
        setDuration(props.getProperty("duration"), null);
        setOutput(props.getProperty("output"), null);
        setStopTrigger(props.getProperty("stopTrigger"), null);
        setLoop(props.getProperty("loop"), null);

        setRange(props.getProperty("range"), null);

        if (!props.hasProperty("stepSize") && !props.hasProperty("stepCount"))
            throw new IllegalArgumentException("Either stepSize or stepCount must have values.");
        if (props.hasProperty("stepSize")) {
            setStepSize(props.getProperty("stepSize"), null);
        } else {
            setStepCount(props.getProperty("stepCount"), null);
        }
        stepVar = new Variable("step", DataType.FLOAT, -1, true);
        setStepVar = new VarTableAction(stepVar.getName(), VarTableAction.Action.SET_ANY);
    }

    public void setLoop(Object value, SetProperty.Flag flag) {
        loop = (Boolean)value;
    } public Object getLoop() { return loop; }

    public void setOutput(Object value, SetProperty.Flag flag) {
        output = (Variable)value;
    } public Object getOutput() { return output; }

    public void setRange(Object value, SetProperty.Flag flag) {
        range = (Range)value;
        double[] ordRange = range.getOrderedRange();
        stepSize = ordRange[1] - ordRange[0];
    } public Object getRange() { return range; }

    public void setStepCount(Object value, SetProperty.Flag flag) {
        if (value==null) return;
        long duration = (Long)getDuration();
        stepCount = (Integer)value;
        if (range != null) {
            double[] r = range.getOrderedRange();
            stepSize = (r[1]-r[0])/stepCount;
        }
        setDuration(duration, null);
    } public Object getStepCount() { return stepCount; }

    public void setStepSize(Object value, SetProperty.Flag flag) {
        if (value==null) return;
        long duration = (Long)getDuration();
        stepSize = (Double)value;
        if (range != null) {
            double[] r = range.getOrderedRange();
            stepCount = (int)Math.round((r[1]-r[0])/stepSize);
        }
        setDuration(duration, null);
    } public Object getStepSize() { return stepSize; }

    public void setDuration(Object value, SetProperty.Flag flag) {
        long duration = (Long)value;
        double delay = (double)(duration/(double)stepCount);
        stepDelay = (int)delay;
        stepDelayNano = (int)Math.round((delay-stepDelay)*1000000);
    } public Object getDuration() {
        double delay = stepDelay + (stepDelayNano/1000000);
        return (Long)Math.round(delay * stepCount);
    }

    public void setStopTrigger(Object value, SetProperty.Flag flag) {
        stopTrigger = (VarMatcher)value;
    } public Object getStopTrigger() { return stopTrigger; }
    
    public void setStartTrigger(Object value, SetProperty.Flag flag) {
        startTrigger = (VarMatcher)value;
    } public Object getStartTrigger() { return startTrigger; }
    
    @Override
    protected void msgReceived(Variable in, long timestamp, Channel channel) {
        if (startTrigger.matches(in, channel, this)) {
            startLine();
        } else if (stopTrigger!=null && stopTrigger.matches(in, channel, this) && running) {
            stopLine();
        }
    }

    @Override
    public void stop() {
        if (running) stopLine();
        super.stop();
    }

    private void sendGraph(double curStep) {
        try {
            stepVar.setValue(curStep, true);
            setStepVar.set(stepVar, localVars, this);
            this.sendToOutputs(output, -1);
        } catch (IllegalArgumentException ie) {
            log(ie.getMessage(), MSG.WARNING);
        }
    }

    private void startLine() {
        if (infoFlag) log("Starting line", MSG.INFO);
        gThread = new GraphThread(this); 
        gThread.start();
        running = true;            

    }

    private void stopLine() {
        if (infoFlag) log("Interrupting line", MSG.INFO);
        gThread.interrupt();
        running = false;            
        gThread = null;            
    }

    class GraphThread extends Thread {            
        Graph g;
        
        public GraphThread(Graph graph) {
            g = graph;
        }
    
        @Override
        public void run() {
            do {
                for (double i=range.getOrderedRange()[0]; i<range.getOrderedRange()[1]; i+=stepSize) {
                    g.sendGraph(i);
                    if ((g.stepDelay*1000000 + g.stepDelayNano)>0) {
                        synchronized(this) {
                            try {
                                this.wait(g.stepDelay, g.stepDelayNano);
                            } catch (InterruptedException ie) { return; }
                        }
                    }
                    if (!g.running) break;
                }
            } while (g.loop || g.running);
        }
    }    
}
